package game.block.game;

import game.block.Constants;
import game.block.Locator;
import game.block.blocks.ExplosionBlock;
import game.block.blocks.JumpBlock;
import game.block.blocks.KingBlock;
import game.block.blocks.SandstoneBlock;
import game.block.blocks.StoneBlock;
import game.block.blocks.WoodBlock;
import game.block.command.Command;
import game.block.entity.Entity;
import game.block.entity.Goal;
import game.block.entity.MovableEntity;
import game.block.entity.TrapEntity;
import game.block.entity.Wall;
import game.block.input.DraggedMove;
import game.block.screen.BlackScreen;
import game.block.screen.BoardScreen;
import game.block.screen.FadeInTransitionEffect;
import game.block.screen.FadeOutTransitionEffect;
import game.block.screen.TransitionEffect;
import game.block.screen.TransitionScreen;
import game.block.tile.Tile;
import game.block.trap.Fire;
import game.block.trap.Hole;
import game.block.trap.Ice;
import game.block.trap.Quicksand;
import game.block.trap.Spike;
import game.block.trap.Teleport;
import game.block.trap.WallTrigger;
import game.block.trap.Wind;
import game.block.turn.Turn;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Stack;
import java.util.StringTokenizer;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;

public class Board implements Constants {
    public static boolean DIED = false;
    public static boolean SPAWN = false;

    private ArrayList<Tile> tiles;
    private ArrayList<Entity> entities;
    private ArrayList<TrapEntity> traps;
    private ArrayList<MovableEntity> movableBlocks;
    private Stack<Turn> turns;
    private ArrayList<Turn> queuedTurns;
    private ArrayList<Entity> renderEntities;
    private Turn currentTurn;
    private int rows;
    private int cols;
    private DraggedMove draggedMove;
    private MovableEntity blockSelected;
    private boolean endOfTurn;
    private float endOfTurnTimer;
    private OrthographicCamera camera;
    private Goal goal;
    private KingBlock kingBlock;
    private boolean removed;
    private float removeTimer;
    private float winTimer;
    private int moves;
    private String selectedInfo;
    private float diedTimer;
    private float spawnTimer;
    private BoardScreen screen;
    private boolean pause;
    private float musicTimer;

    public Board(OrthographicCamera camera) {
        tiles = new ArrayList<Tile>();
        turns = new Stack<Turn>();
        entities = new ArrayList<Entity>();
        movableBlocks = new ArrayList<MovableEntity>();
        queuedTurns = new ArrayList<Turn>();
        traps = new ArrayList<TrapEntity>();

        pause = false;
        renderEntities = new ArrayList<Entity>();
        this.camera = camera;
    }

    public void initNewBoard(int level) throws IOException {
        screen.getTutorial().pickLevel(level);
        blockSelected = null;
        endOfTurn = false;
        removeTimer = 0;
        winTimer = 0;
        moves = 0;
        musicTimer = 0;
        removed = false;
        selectedInfo = "";
        currentTurn = new Turn();
        tiles.clear();
        traps.clear();
        turns.clear();
        queuedTurns.clear();
        movableBlocks.clear();
        entities.clear();

        Tile[] spawns = new Tile[6];
        ArrayList<Teleport> teleports= new ArrayList<Teleport>();
        ArrayList<Wall> triggers = new ArrayList<Wall>();
        ArrayList<WallTrigger> triggerWalls = new ArrayList<WallTrigger>();

        Locator.getEffects().clear();
        BufferedReader br = new BufferedReader(Gdx.files.internal("data/levels/level" + level + ".txt").reader());
        String info=null;
		try {
			info = br.readLine();
		} catch (IOException e) {
			screen.backToMenu();
		}
        rows = Integer.parseInt(info);
        info = br.readLine();
        cols = Integer.parseInt(info);
        draggedMove = new DraggedMove(tiles, rows, cols);
        // update camera pos to middle of board
        camera.position.set(new Vector3(rows / 2 * TILE_SIZE, cols / 2 * TILE_SIZE, 0));
        camera.update();
        for (int j = cols - 1; j >= 0; j--) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int i = 0; i < rows; i++) {
                float x = i * TILE_SIZE;
                float y = j * TILE_SIZE;
                // create tile for every board space
                Tile tile = new Tile(new Vector2(x, y));
                Entity entity = null;
                String data = st.nextToken();
                switch (data.charAt(0)) {
                case 'w':
                    float direction = 0;
                    if (i == 0)
                        direction = 90;
                    if (i == rows - 1)
                        direction = -90;
                    if (j == 0)
                        direction = 180;
                    // if this wall is connected to a trigger, set an id
                    if (data.length() > 1) {
                        entity = new Wall(new Vector2(x, y), tile, direction, (int) data.charAt(1));
                        triggers.add((Wall) entity);
                    } else {
                        entity = new Wall(new Vector2(x, y), tile, direction);
                    }
                    entities.add(entity);

                    break;
                case 'b': // blank tile
                    break;
                case 'k':
                    entity = new KingBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    kingBlock = (KingBlock) entity;
                    entities.add(entity);
                    break;
                case 'o':
                    entity = new WoodBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    entities.add(entity);
                    break;
                case 'j':
                    entity = new JumpBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    entities.add(entity);
                    break;
                case 'x':
                    entity = new ExplosionBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    entities.add(entity);
                    break;
                case 't':
                    entity = new StoneBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    entities.add(entity);
                    break;
                case 'a':
                    entity = new SandstoneBlock(new Vector2(x, y), tile);
                    movableBlocks.add((MovableEntity) entity);
                    entities.add(entity);
                    break;
                case 'q':
                    entity = new Quicksand(new Vector2(x, y), tile);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;
                case 's':
                    entity = new Spike(new Vector2(x, y), tile, Character.getNumericValue(data.charAt(1)));
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;
                case 'f':
                    entity = new Fire(new Vector2(x, y), tile, data.charAt(1), camera);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;
                case 'd':
                    entity = new Wind(new Vector2(x, y), tile, data.charAt(1), camera, this);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;

                case 'h':
                    entity = new Hole(new Vector2(x, y), tile);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;
                case 'i':
                    entity = new Ice(new Vector2(x, y), tile);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    break;
                case 'p':
                    int id = Character.getNumericValue(data.charAt(1));
                    entity = new Teleport(new Vector2(x, y), tile, id, camera);
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    teleports.add((Teleport) entity);
                    break;
                case 'n':
                    id = Character.getNumericValue(data.charAt(1));
                    spawns[id]=tile;
                    break;
                case 'g':
                    entity = new WallTrigger(new Vector2(x, y), tile, (int) data.charAt(1));
                    traps.add((TrapEntity) entity);
                    entities.add(entity);
                    triggerWalls.add((WallTrigger) entity);
                    break;
                case 'l':
                    entity = new Goal(new Vector2(x, y), tile);
                    goal = (Goal) entity;
                    entities.add(entity);
                    break;
                }
                if (entity != null) {
                    tile.add(entity);
                }
                tiles.add(tile);
            }
        }
        // add second tile to fire traps
        for (TrapEntity t : traps) {
            if (t instanceof Fire) {
                ((Fire) t).initSecondTile(tiles, this);
            }
        }
        for (TrapEntity t : traps) {
            if (t instanceof Wind) {
                ((Wind) t).initSecondTile(tiles, this);
            }
        }
        for (int i = 0; i < teleports.size(); i++) {
        	for(int j=0; j<spawns.length; j++){
        		if(teleports.get(i).getId() == j){
        			teleports.get(i).setSpawnTile(spawns[j]);
        		}
        	}
        }
        for (int i = 0; i < triggerWalls.size(); i++) {
            for (int j = 0; j < triggers.size(); j++) {
                if (triggerWalls.get(i).getId() == triggers.get(j).getId()) {
                    triggerWalls.get(i).setWall(triggers.get(j));
                }
            }
        }
    }

    public void advanceTurn() {
        // advance traps, then execute the turn
        for (TrapEntity t : traps) {
            Command c = t.advance();
            if (c != null) {
                currentTurn.addCommand(c);
            }
        }
        turns.add(currentTurn);
        currentTurn.execute();
        currentTurn = new Turn();
        moves++;
    }

    public void turnOver() {
        if (currentTurn.getSize() != 0) {
            queuedTurns.add(0, currentTurn);
        }
        if (queuedTurns.size() != 0) {
            currentTurn = queuedTurns.get(0);
            queuedTurns.remove(0);
        }
        endOfTurnTimer = 0;
        endOfTurn = true;
        advanceTurn();
    }

    public void undo() {
        if (!turns.empty()) {
            if (blockSelected != null) {
                blockSelected.unhighlightAdjacent();
            }
            // undo turn, then retract traps
            turns.pop().undo();
            moves--;
            for (TrapEntity t : traps) {
                t.retract();
            }
            if (blockSelected != null) {
                blockSelected.start(this);
                blockSelected.highlightAdjacent();
            }
        }
    }

    public void removeEntity(Entity entity) {
        renderEntities.add(entity);
        removeTimer = 0;
        removed = true;
    }

    public void select(float x, float y) {
        for (Tile t : tiles) {
            if (x > t.getPosition().x && x < t.getPosition().x + TILE_SIZE) {
                if (y > t.getPosition().y && y < t.getPosition().y + TILE_SIZE) {
                    // if we have a block selected, move only to the available
                    // tiles
                    if (blockSelected != null) {
                        draggedMove.startDrag(blockSelected.getCurrentTile());
                        // if a valid space to move into was selected
                        if (blockSelected.select(currentTurn, t, this)) {
                        }
                        // select a different block
                        else if (t.containsBlock()) {
                            // unhighlight tiles first
                            blockSelected.unhighlightAdjacent();
                            unhideAllTiles();
                            selectABlock(t);
                        }
                        // deselect a block by clicking on a wall or out of
                        // board, but not on the current block
                        else if (!blockSelected.getCurrentTile().equals(t)) {
                            blockSelected.unhighlightAdjacent();
                            unhideAllTiles();
                            setBlockSelected(null);
                            if (t.getEntities().size() > 0) {
                                setSelectedInfo(t.getEntities().get(0).getInfo());
                            }
                        }
                        // click on itself
                        else {
                            setSelectedInfo(blockSelected.getInfo());
                            blockSelected.start(this);
                        }
                    }
                    // select a block if we have nothing selected
                    else if (t.containsBlock()) {
                        selectABlock(t);
                    } else {
                        if (t.getEntities().size() > 0) {
                            setSelectedInfo(t.getEntities().get(0).getInfo());
                        }
                    }
                }
            }
        }
    }

    public void selectABlock(Tile t) {
        blockSelected = (MovableEntity) t.getBlock();
        setSelectedInfo(blockSelected.getInfo());
        blockSelected.start(this);
        blockSelected.highlightAdjacent();
        addBlockToEnd(blockSelected);
        draggedMove.startDrag(blockSelected.getCurrentTile());
    }

    public void render(SpriteBatch sb) {
        for (Tile t : tiles) {
            t.render(sb);
        }
        for (Entity e : entities) {
            e.render(sb);
        }
        if (removed) {
            for (Entity e : renderEntities) {
                e.render(sb);
            }
        }
    }

    public void update(float delta) {
        if (endOfTurn) {
            endOfTurnTimer += delta;
            // end the turn when the timer runs out
            if (endOfTurnTimer > TURN_LENGTH) {
                // if we still have queued up turns, advance them
                if (queuedTurns.size() != 0) {
                    turnOver();
                } else {
                    endOfTurn = false;
                    if (blockSelected != null) {
                        blockSelected.start(this);
                        blockSelected.highlightAdjacent();
                    }
                }
            }
        }
        if (!pause) {
            // update entities
            for (Tile t : tiles) {
                t.update(delta);
            }
            for (Entity e : entities) {
                e.update(delta);
            }
        }

        // pick a random track when current is done playing
        if (Locator.getAudio().getMusic() != null && !Locator.getAudio().getMusic().isPlaying()) {
            musicTimer += delta;
            if (musicTimer > 2) {
                Locator.getAudio().pickMusic();
                musicTimer = 0;
            }
        }
        // move to the next level when king reaches flag
        if (goal.getCurrentTile().equals(kingBlock.getCurrentTile())) {
            winTimer += delta;
            if (winTimer > TURN_LENGTH * 1.5f) {
                Locator.getAudio().playSound(SOUND.FLAG.getValue());
                nextLevel();
            }
        }
        // show block but dont update it when it dies to a trap
        if (removed) {
            removeTimer += delta;
            for (Entity e : renderEntities) {
                e.update(delta);
            }
            if (removeTimer > TURN_LENGTH *1.5f) {
                removed = false;
                renderEntities.clear();
            }
        }
        // undo if king dies and fade to black
        if (DIED) {
            diedTimer += delta;
            if (diedTimer > TURN_LENGTH && removed) {
                die();
                diedTimer = 0;
            }
        }
        // spawn king and fade out of black
        if (SPAWN) {
            spawnTimer += delta;
            if (spawnTimer > 0.0f) {
                spawn();
            }
        }
    }

    public void die() {
        ArrayList<TransitionEffect> effects = new ArrayList<TransitionEffect>();
        effects.add(new FadeOutTransitionEffect(0.5f));
        Screen transitionScreen = new TransitionScreen(Locator.getGame(), screen, new BlackScreen(this), effects);
        Locator.getGame().setScreen(transitionScreen);

        Locator.getAudio().playSound(SOUND.DIEING.getValue());
        DIED = false;
    }

    public void spawn() {
        Locator.getAudio().playSound(SOUND.RESPAWN.getValue());
        SPAWN = false;
    }

    public void nextLevel() {
        try {
        	//if the next level exists, go to it, otherwise go back to menu
        	if(Locator.getLevel() < 15){
        		Locator.nextLevel();
        		initNewBoard(Locator.getLevel());
        		screen.updateTutorialTable();
        		screen.updateBounds();
        	} else {
        		Locator.setLevel(1);
        		initNewBoard(1);
        		screen.updateTutorialTable();
        		screen.updateBounds();
        	}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void unhighlightAllTiles() {
        for (Tile t : tiles) {
            t.changeColor(COLOR.BLANK);
        }
    }

    public void addBlockToEnd(MovableEntity block) {
        if (entities.remove(block)) {
            entities.add(block);
        }
    }

    public void restart() throws IOException {
        initNewBoard(Locator.getLevel());
    }

    public void setScreen(BoardScreen s) {
        screen = s;
    }

    public void setSelectedInfo(String s) {
        selectedInfo = s;
    }

    public String getSelectedInfo() {
        return selectedInfo;
    }

    public ArrayList<Turn> getQueuedTurns() {
        return queuedTurns;
    }

    public void setQueuedTurns(ArrayList<Turn> queuedTurns) {
        this.queuedTurns = queuedTurns;
    }

    public ArrayList<Tile> getTiles() {
        return tiles;
    }

    public ArrayList<MovableEntity> getMovableBlocks() {
        return movableBlocks;
    }

    public void setMovableBlocks(ArrayList<MovableEntity> movableBlocks) {
        this.movableBlocks = movableBlocks;
    }

    public void setTiles(ArrayList<Tile> tiles) {
        this.tiles = tiles;
    }

    public ArrayList<Entity> getEntities() {
        return entities;
    }

    public void setEntities(ArrayList<Entity> entities) {
        this.entities = entities;
    }

    public boolean isEndOfTurn() {
        return endOfTurn;
    }

    public void setEndOfTurn(boolean endOfTurn) {
        this.endOfTurn = endOfTurn;
    }

    public Stack<Turn> getTurns() {
        return turns;
    }

    public void setTurns(Stack<Turn> turns) {
        this.turns = turns;
    }

    public int getMoves() {
        return moves;
    }

    public MovableEntity getBlockSelected() {
        return blockSelected;
    }

    public void setBlockSelected(MovableEntity blockSelected) {
        if (this.blockSelected != null) {
            this.blockSelected.unhighlightAdjacent();
        }
        this.blockSelected = blockSelected;
    }

    public ArrayList<TrapEntity> getTraps() {
        return traps;
    }

    public void setTraps(ArrayList<TrapEntity> traps) {
        this.traps = traps;
    }

    public OrthographicCamera getCamera() {
        return camera;
    }

    public void setCamera(OrthographicCamera camera) {
        this.camera = camera;
    }

    public DraggedMove getDraggedMove() {
        return draggedMove;
    }

    public void setDraggedMove(DraggedMove draggedMove) {
        this.draggedMove = draggedMove;
    }

    public int getRows() {
        return rows;
    }

    public void setRows(int rows) {
        this.rows = rows;
    }

    public int getCols() {
        return cols;
    }

    public void setCols(int cols) {
        this.cols = cols;
    }

    public void unhideAllTiles() {
        for (Entity e : entities) {
            e.unhideSprite();
        }

    }

    public BoardScreen getScreen() {
        return screen;
    }

    public void setPause(boolean p) {
        pause = p;
    }

    public void returnToMenu() {
        try {
            this.restart();
        } catch (IOException e) {
            e.printStackTrace();
        }
        screen.pause();
        ArrayList<TransitionEffect> effects = new ArrayList<TransitionEffect>();
        effects.add(new FadeOutTransitionEffect(0.5f));
        effects.add(new FadeInTransitionEffect(0.5f));
        Locator.getAudio().playSound(SOUND.HOVER.getValue());
        Screen transitionScreen = new TransitionScreen(Locator.getGame(), screen, Locator.getGameState().getMainMenu(), effects);
        Locator.getGame().setScreen(transitionScreen);
    }

}
